{/* Copyright 2020 Adobe. All rights reserved.
This file is licensed to you under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License. You may obtain a copy
of the License at http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software distributed under
the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
OF ANY KIND, either express or implied. See the License for the specific language
governing permissions and limitations under the License. */}

import {Layout} from '@react-spectrum/docs';
export default Layout;

import docs from 'docs:@react-aria/tag';
import utilsDocs from 'docs:@react-aria/utils';
import typesDocs from 'docs:@react-types/shared/src/events.d.ts';
import {HeaderInfo, FunctionAPI, TypeContext, InterfaceType, TypeLink, PageDescription} from '@react-spectrum/docs';
import {Keyboard} from '@react-spectrum/text';
import packageData from '@react-aria/tag/package.json';
import ChevronRight from '@spectrum-icons/workflow/ChevronRight';
import tailwindExample from 'url:./tailwind.png';
import {ExampleCard} from '@react-spectrum/docs/src/ExampleCard';
import Anatomy from './anatomy.svg';

---
category: Collections
keywords: [tag, aria, grid]
---

# useTagGroup

<PageDescription>{docs.exports.useTagGroup.description}</PageDescription>

<HeaderInfo
  packageData={packageData}
  componentNames={['useTagGroup']}
  sourceData={[
    {type: 'W3C', url: 'https://www.w3.org/WAI/ARIA/apg/patterns/grid/'}
  ]} />

## API

<FunctionAPI function={docs.exports.useTagGroup} links={docs.links} />
<FunctionAPI function={docs.exports.useTag} links={docs.links} />

## Features

* Exposed to assistive technology as a grid using ARIA
* Keyboard navigation support including arrow keys, home/end, page up/down, and delete
* Keyboard focus management and cross browser normalization
* Labeling support for accessibility
* Support for mouse, touch, and keyboard interactions

## Anatomy

<Anatomy />

A tag group consists of a list of tags.
If a visual label is not provided, then an `aria-label` or
`aria-labelledby` prop must be passed to identify the tag group to assistive technology.

Individual tags should include a visual label, and may optionally include icons or a remove button.

`useTagGroup` returns props for the group and its label, which you should spread
onto the appropriate element:

<TypeContext.Provider value={docs.links}>
  <InterfaceType properties={docs.links[docs.exports.useTagGroup.return.id].properties} />
</TypeContext.Provider>

`useTag` returns props for an individual tag, along with states that you can use for styling:

<TypeContext.Provider value={docs.links}>
  <InterfaceType properties={docs.links[docs.exports.useTag.return.id].properties} />
</TypeContext.Provider>

In order to be correctly identified to assistive technologies and enable proper keyboard navigation, the tag group should use `gridProps` on its outer container.

Each individual tag should use `rowProps` on its outer container, and use `gridCellProps` on an inner container.

## Example

```tsx example export=true
import type {AriaTagGroupProps, AriaTagProps} from '@react-aria/tag';
import type {ListState} from '@react-stately/list';
import {useTag, useTagGroup} from '@react-aria/tag';
import {useListState} from '@react-stately/list';
import {Item} from '@react-stately/collections';
import {useFocusRing} from '@react-aria/focus';

// Reuse the Button from your component library. See below for details.
import {Button} from 'your-component-library';

function TagGroup<T extends object>(props: AriaTagGroupProps<T>) {
  let { label, description, errorMessage } = props;
  let ref = React.useRef(null);

  let state = useListState(props);
  let {
    gridProps,
    labelProps,
    descriptionProps,
    errorMessageProps
  } = useTagGroup(props, state, ref);

  return (
    <div className="tag-group">
      <div {...labelProps}>{label}</div>
      <div {...gridProps} ref={ref}>
        {[...state.collection].map((item) => (
          <Tag
            key={item.key}
            item={item}
            state={state} />
        ))}
      </div>
      {description && (
        <div {...descriptionProps} className="description">
          {description}
        </div>
      )}
      {errorMessage && (
        <div {...errorMessageProps} className="error-message">
          {errorMessage}
        </div>
      )}
    </div>
  );
}

interface TagProps<T> extends AriaTagProps<T> {
  state: ListState<T>
}

function Tag<T>(props: TagProps<T>) {
  let {item, state} = props;
  let ref = React.useRef(null);
  let {focusProps, isFocusVisible} = useFocusRing({within: false});
  let {rowProps, gridCellProps, removeButtonProps, allowsRemoving} = useTag(props, state, ref);

  return (
    <div ref={ref} {...rowProps} {...focusProps} data-focus-visible={isFocusVisible}>
      <div {...gridCellProps}>
        {item.rendered}
        {allowsRemoving && <Button {...removeButtonProps}>ⓧ</Button>}
      </div>
    </div>
  );
}

<TagGroup label="Categories">
  <Item key="news">News</Item>
  <Item key="travel">Travel</Item>
  <Item key="gaming">Gaming</Item>
  <Item key="shopping">Shopping</Item>
</TagGroup>
```

<details>
  <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show CSS</summary>

```css
.tag-group {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.tag-group [role="grid"] {
  display: flex;
  flex-wrap: wrap;
  gap: 4px;
}

.tag-group [role="row"] {
  border: 1px solid gray;
  forced-color-adjust: none;
  border-radius: 4px;
  padding: 2px 8px;
  font-size: 0.929rem;
  outline: none;
  cursor: default;
  display: flex;
  align-items: center;
  transition: border-color 200ms;

  &[data-focus-visible=true] {
    outline: 2px solid slateblue;
    outline-offset: 2px;
  }

  &[aria-selected=true] {
    background: var(--spectrum-gray-900);
    border-color: var(--spectrum-gray-900);
    color: var(--spectrum-gray-50);
  }

  &[aria-disabled] {
    opacity: 0.4;
  }
}

.tag-group [role="gridcell"] {
  display: contents;
}

.tag-group [role="row"] button {
  background: none;
  border: none;
  padding: 0;
  margin-left: 4px;
  outline: none;
  font-size: 0.95em;
  border-radius: 100%;
  aspect-ratio: 1/1;
  height: 100%;

  &[data-focus-visible=true] {
    outline: 2px solid slateblue;
    outline-offset: -1px;
  }
}

.tag-group .description {
  font-size: 12px;
}

.tag-group .error-message {
  color: red;
  font-size: 12px;
}
```

</details>

### Button

The `Button` component is used in the above example to remove a tag. It is built using the [useButton](../Button/useButton.html) hook, and can be shared with many other components.

<details>
  <summary style={{fontWeight: 'bold'}}><ChevronRight size="S" /> Show code</summary>

```tsx example export=true render=false
import {useButton} from '@react-aria/button';
import {mergeProps} from '@react-aria/utils';

function Button(props) {
  let ref = React.useRef(null);
  let {buttonProps} = useButton(props, ref);
  let {focusProps, isFocusVisible} = useFocusRing({within: false});
  return <button {...mergeProps(buttonProps, focusProps)} ref={ref} data-focus-visible={isFocusVisible}>{props.children}</button>;
}
```

</details>


## Styled examples

<ExampleCard
  url="https://codesandbox.io/s/usetaggroup-with-tailwind-css-zxxrpv"
  preview={tailwindExample}
  title="Tailwind CSS"
  description="A TagGroup built with Tailwind." />

## Usage

### Remove tags

The `onRemove` prop can be used to include a remove button which can be used to remove a tag. This allows the user to press the remove button, or press the backspace key while the tag is focused to remove the tag from the group. Additionally, when [selection](#selection) is enabled, all selected items will be deleted when pressing the backspace key on a selected tag.

```tsx example
import {useListData} from '@react-stately/data';

function Example() {
  let list = useListData({
    initialItems: [
      { id: 1, name: "News" },
      { id: 2, name: "Travel" },
      { id: 3, name: "Gaming" },
      { id: 4, name: "Shopping" }
    ]
  });

  return (
    <TagGroup
      label="Categories"
      items={list.items}
      onRemove={(keys) => list.remove(...keys)}>
      {(item) => <Item>{item.name}</Item>}
    </TagGroup>
  );
}
```

### Selection

TagGroup supports multiple selection modes. By default, selection is disabled, however this can be changed using the `selectionMode` prop.
Use `defaultSelectedKeys` to provide a default set of selected items (uncontrolled) and `selectedKeys` to set the selected items (controlled). The value of the selected keys must match the `key` prop of the items.
See the `react-stately` [Selection docs](v3:selection.html) for more details.

```tsx example
import type {Selection} from 'react-stately';

function Example() {
  let [selected, setSelected] = React.useState<Selection>(new Set(['parking']));

  return (
    <>
      <TagGroup label="Amenities" selectionMode="multiple" selectedKeys={selected} onSelectionChange={setSelected}>
        <Item key="laundry">Laundry</Item>
        <Item key="fitness">Fitness center</Item>
        <Item key="parking">Parking</Item>
        <Item key="pool">Swimming pool</Item>
        <Item key="breakfast">Breakfast</Item>
      </TagGroup>
      <p>Current selection (controlled): {selected === 'all' ? 'all' : [...selected].join(', ')}</p>
    </>
  );
}
```

### Links

Tags may be links to another page or website. This can be achieved by passing the `href` prop to the `<Item>` component. Tags with an `href` are not selectable.

```tsx example
<TagGroup label="Links">
  <Item href="https://adobe.com/" target="_blank">Adobe</Item>
  <Item href="https://apple.com/" target="_blank">Apple</Item>
  <Item href="https://google.com/" target="_blank">Google</Item>
  <Item href="https://microsoft.com/" target="_blank">Microsoft</Item>
</TagGroup>
```

```css hidden
.tag-group [role="row"][data-href] {
  cursor: pointer;
}
```

#### Client side routing

The `<Item>` component works with frameworks and client side routers like [Next.js](https://nextjs.org/) and [React Router](https://reactrouter.com/en/main). As with other React Aria components that support links, this works via the <TypeLink links={utilsDocs.links} type={utilsDocs.exports.RouterProvider} /> component at the root of your app. See the [framework setup guide](../frameworks) to learn how to set this up.

### Disabled tags

TagGroup supports marking items as disabled using the `disabledKeys` prop. Each key in this list
corresponds with the `key` prop passed to the `Item` component, or automatically derived from the values passed
to the `items` prop. Disabled items are not focusable, selectable, or keyboard navigable.
See [Collections](v3:collections.html) for more details.

```tsx example
<TagGroup label="Sandwich contents" selectionMode="multiple" disabledKeys={['tuna']}>
  <Item key="lettuce">Lettuce</Item>
  <Item key="tomato">Tomato</Item>
  <Item key="cheese">Cheese</Item>
  <Item key="tuna">Tuna Salad</Item>
  <Item key="egg">Egg Salad</Item>
  <Item key="ham">Ham</Item>
</TagGroup>
```

### Description

The `description` prop can be used to associate additional help text with a tag group.

```tsx example
<TagGroup label="Categories" description="Your selected categories.">
  <Item key="news">News</Item>
  <Item key="travel">Travel</Item>
  <Item key="gaming">Gaming</Item>
  <Item key="shopping">Shopping</Item>
</TagGroup>
```

### Error message

The `errorMessage` prop can be used to help the user fix a validation error.

```tsx example
<TagGroup label="Categories" errorMessage="Invalid set of categories.">
  <Item key="news">News</Item>
  <Item key="travel">Travel</Item>
  <Item key="gaming">Gaming</Item>
  <Item key="shopping">Shopping</Item>
</TagGroup>
```
